import { Handler } from '../../core/Handler';
import * as DomUtil from '../../dom/DomUtil';
import { Draggable } from '../../dom/Draggable';
import { toBounds } from '../../geometry/Bounds';
import { toPoint } from '../../geometry/Point';
import { requestAnimFrame, cancelAnimFrame } from '../../core/Util';

/*
 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
 */

/* @namespace Marker
 * @section Interaction handlers
 *
 * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
 *
 * ```js
 * marker.dragging.disable();
 * ```
 *
 * @property dragging: Handler
 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
 */

export var MarkerDrag = Handler.extend({
  initialize: function (marker) {
    this._marker = marker;
  },

  addHooks: function () {
    var icon = this._marker._icon;

    if (!this._draggable) {
      this._draggable = new Draggable(icon, icon, true);
    }

    this._draggable
      .on(
        {
          dragstart: this._onDragStart,
          predrag: this._onPreDrag,
          drag: this._onDrag,
          dragend: this._onDragEnd,
        },
        this,
      )
      .enable();

    DomUtil.addClass(icon, 'leaflet-marker-draggable');
  },

  removeHooks: function () {
    this._draggable
      .off(
        {
          dragstart: this._onDragStart,
          predrag: this._onPreDrag,
          drag: this._onDrag,
          dragend: this._onDragEnd,
        },
        this,
      )
      .disable();

    if (this._marker._icon) {
      DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
    }
  },

  moved: function () {
    return this._draggable && this._draggable._moved;
  },

  _adjustPan: function (e) {
    var marker = this._marker,
      map = marker._map,
      speed = this._marker.options.autoPanSpeed,
      padding = this._marker.options.autoPanPadding,
      iconPos = DomUtil.getPosition(marker._icon),
      bounds = map.getPixelBounds(),
      origin = map.getPixelOrigin();

    var panBounds = toBounds(
      bounds.min._subtract(origin).add(padding),
      bounds.max._subtract(origin).subtract(padding),
    );

    if (!panBounds.contains(iconPos)) {
      // Compute incremental movement
      var movement = toPoint(
        (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) /
          (bounds.max.x - panBounds.max.x) -
          (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) /
            (bounds.min.x - panBounds.min.x),

        (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) /
          (bounds.max.y - panBounds.max.y) -
          (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) /
            (bounds.min.y - panBounds.min.y),
      ).multiplyBy(speed);

      map.panBy(movement, { animate: false });

      this._draggable._newPos._add(movement);
      this._draggable._startPos._add(movement);

      DomUtil.setPosition(marker._icon, this._draggable._newPos);
      this._onDrag(e);

      this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
    }
  },

  _onDragStart: function () {
    // @section Dragging events
    // @event dragstart: Event
    // Fired when the user starts dragging the marker.

    // @event movestart: Event
    // Fired when the marker starts moving (because of dragging).

    this._oldLatLng = this._marker.getLatLng();

    // When using ES6 imports it could not be set when `Popup` was not imported as well
    this._marker.closePopup && this._marker.closePopup();

    this._marker.fire('movestart').fire('dragstart');
  },

  _onPreDrag: function (e) {
    if (this._marker.options.autoPan) {
      cancelAnimFrame(this._panRequest);
      this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
    }
  },

  _onDrag: function (e) {
    var marker = this._marker,
      shadow = marker._shadow,
      iconPos = DomUtil.getPosition(marker._icon),
      latlng = marker._map.layerPointToLatLng(iconPos);

    // update shadow position
    if (shadow) {
      DomUtil.setPosition(shadow, iconPos);
    }

    marker._latlng = latlng;
    e.latlng = latlng;
    e.oldLatLng = this._oldLatLng;

    // @event drag: Event
    // Fired repeatedly while the user drags the marker.
    marker.fire('move', e).fire('drag', e);
  },

  _onDragEnd: function (e) {
    // @event dragend: DragEndEvent
    // Fired when the user stops dragging the marker.

    cancelAnimFrame(this._panRequest);

    // @event moveend: Event
    // Fired when the marker stops moving (because of dragging).
    delete this._oldLatLng;
    this._marker.fire('moveend').fire('dragend', e);
  },
});
